//go:build !windows

package dnsproxy

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"slices"
	"strconv"
	"strings"

	"github.com/telepresenceio/telepresence/v2/pkg/ioutil"
)

type ResolveFile struct {
	Port        int
	Domain      string
	Nameservers []string
	Search      []string
	Options     []string
}

func ReadResolveFile(fileName string) (*ResolveFile, error) {
	fl, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer fl.Close()
	sc := bufio.NewScanner(fl)
	rf := ResolveFile{}
	line := 0

	onlyOne := func(key string) error {
		return fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
	}

	for sc.Scan() {
		line++
		txt := strings.TrimSpace(sc.Text())
		if len(txt) == 0 || strings.HasPrefix(txt, "#") {
			continue
		}
		fields := strings.Fields(txt)
		fc := len(fields)
		if fc == 0 {
			continue
		}
		key := fields[0]
		if fc == 1 {
			return nil, fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
		}
		value := fields[1]
		switch key {
		case "port":
			if fc != 2 {
				return nil, onlyOne(key)
			}
			rf.Port, err = strconv.Atoi(value)
			if err != nil {
				return nil, fmt.Errorf("%q is not a valid integer at %s line %d", key, fileName, line)
			}
		case "domain":
			if fc != 2 {
				return nil, onlyOne(key)
			}
			rf.Domain = value
		case "nameserver":
			if fc != 2 {
				return nil, onlyOne(key)
			}
			rf.Nameservers = append(rf.Nameservers, value)
		case "search":
			rf.Search = fields[1:]
		case "options":
			rf.Options = fields[1:]
		default:
			// This reader doesn't do options just yet
			return nil, fmt.Errorf("%q is not a recognized key at %s line %d", key, fileName, line)
		}
	}
	return &rf, nil
}

func (r *ResolveFile) String() string {
	var buf strings.Builder
	_, _ = r.WriteTo(&buf)
	return buf.String()
}

func (r *ResolveFile) Equals(o *ResolveFile) bool {
	if r == nil || o == nil {
		return r == o
	}
	return r.Port == o.Port &&
		r.Domain == o.Domain &&
		slices.Equal(r.Nameservers, o.Nameservers) &&
		slices.Equal(r.Search, o.Search) &&
		slices.Equal(r.Options, o.Options)
}

func (r *ResolveFile) Write(fileName string) error {
	var buf bytes.Buffer
	_, _ = r.WriteTo(&buf)
	return os.WriteFile(fileName, buf.Bytes(), 0o644)
}

func (r *ResolveFile) WriteTo(buf io.Writer) (int64, error) {
	n := ioutil.Println(buf, "# Generated by telepresence\n")
	if r.Port > 0 {
		n += ioutil.Printf(buf, "port %d\n", r.Port)
	}
	if r.Domain != "" {
		n += ioutil.Printf(buf, "domain %s\n", r.Domain)
	}
	for _, ns := range r.Nameservers {
		n += ioutil.Printf(buf, "nameserver %s\n", ns)
	}
	if len(r.Search) > 0 {
		n += ioutil.Printf(buf, "search %s\n", strings.Join(r.Search, " "))
	}
	if len(r.Options) > 0 {
		n += ioutil.Printf(buf, "options %s\n", strings.Join(r.Options, " "))
	}
	return int64(n), nil
}
